import math
import traceback
from datetime import datetime, timedelta
from multiprocessing.pool import ThreadPool
from chinese_calendar import is_holiday

from com.stock.mainStock.stockList import getCompareStockList, stock_list
from com.stock.stockKline import getXueQiuKlineBefore, getXueQiuKlineNormal
from com.util.loggingUtil import Logs
from com.util.tcp.tcpService import TcpService

rightMap = {}
Log = Logs()

TIME_FORMAT = '%Y%m%d'


def getPreviousTradedays(date):
    # 字符串格式日期的处理
    if type(date) == str:
        date = datetime.strptime(date, TIME_FORMAT).date()
        # 获取前一天
        date -= timedelta(days=1)
    while True:
        if is_holiday(date) or date.weekday() == 5 or date.weekday() == 6:
            date -= timedelta(days=1)
        else:
            break
    return str(date).replace('-', '')


def getNextTradedays(date):
    # 字符串格式日期的处理
    if type(date) == str:
        date = datetime.strptime(date, TIME_FORMAT).date()
        # 获后前一天
        date += timedelta(days=1)
    while True:
        if is_holiday(date) or date.weekday() == 5 or date.weekday() == 6:
            date += timedelta(days=1)
        else:
            break
    return str(date).replace('-', '')


def count_exchange_ratio(xqDataList, standardDay, limit):
    exchange_ratio_all = 0.0
    standardDay = standardDay.replace('-', '')
    while True:
        if xqDataList.__contains__(standardDay):
            exchange_ratio = float(xqDataList[standardDay]['exchange_ratio'])
            exchange_ratio_all += exchange_ratio
        else:
            break
        standardDay = getPreviousTradedays(standardDay)
        limit -= 1
        if limit < 1:
            break
    return exchange_ratio_all


def get_min_exchange_ratio(xqDataList, standardDay, limit):
    exchange_ratio_min = 100.0
    standardDay = standardDay.replace('-', '')
    while True:
        if xqDataList.__contains__(standardDay):
            exchange_ratio = float(xqDataList[standardDay]['exchange_ratio'])
            if exchange_ratio < exchange_ratio_min:
                exchange_ratio_min = exchange_ratio
        else:
            break
        standardDay = getPreviousTradedays(standardDay)
        limit -= 1
        if limit < 1:
            break
    return exchange_ratio_min


# 返回standardDay前推limit日的振幅
def get_amplitude_limit(xqDataList, standardDay, limit):
    standardDay = standardDay.replace('-', '')
    amplitude_limit = 0.0
    while True:
        if xqDataList.__contains__(standardDay):
            amplitude_limit = float(xqDataList[standardDay]['highest_price']) - float(
                xqDataList[standardDay]['lowest_price'])

            toDay_close = float(xqDataList[standardDay]['close_price'])
            toDay_lowest = float(xqDataList[standardDay]['lowest_price'])
            toDay_fudu = float(xqDataList[standardDay]['change_pct'])

            standardDay = getPreviousTradedays(standardDay)
            limit -= 1
            if limit < 1:
                break
        else:
            break

    if toDay_lowest == toDay_close and abs(toDay_fudu) > 9.5:
        # 若当天跌停
        return 0.0
    return amplitude_limit


# 获取standardDay前推limit日的最小价格
def get_lowest_price(xqDataList, standardDay, limit):
    standardDay = standardDay.replace('-', '')
    lowest_price = 0.0
    while True:
        if xqDataList.__contains__(standardDay):
            lowest_price = float(xqDataList[standardDay]['lowest_price'])
            standardDay = getPreviousTradedays(standardDay)
            limit -= 1
            if limit < 1:
                break
        else:
            break
    return lowest_price


def is_10_min(xqDataList, standardDay, amplitude_limit_1):
    # 比较值为0.0直接排除
    if amplitude_limit_1 == 0.0:
        return False

    for i in range(0, 10):
        com_val = get_amplitude_limit(xqDataList, standardDay, i)
        if amplitude_limit_1 > com_val:
            Log.info(f"触发振幅额度【{amplitude_limit_1}】>= [{com_val}] ")
            return False
    return True


def is_10_minP(xqDataList, standardDay, lowest_price):
    for i in range(0, 10):
        com_val = get_lowest_price(xqDataList, standardDay, i)
        if lowest_price > com_val:
            Log.info(f"触发最小值【{lowest_price}】> [{com_val}] ")
            return False
    return True


def checkRes(xqDataList, code, standardDay):
    count = int(0)
    # 当天收盘价
    toDay_close = float(xqDataList[standardDay]['close_price'])
    # 之后3日的均价
    nextDay = getNextTradedays(standardDay)
    next2Day = getNextTradedays(nextDay)
    next3Day = getNextTradedays(next2Day)
    try:
        avg_1 = get_avg_1(xqDataList, nextDay)
        if avg_1 > toDay_close:
            count = count + 1

        avg_2 = get_avg_1(xqDataList, next2Day)
        if avg_2 > toDay_close:
            count = count + 1

        avg_3 = get_avg_1(xqDataList, next3Day)
        if avg_3 > toDay_close:
            count = count + 1
        # Log.info(f"{code},{standardDay}后N天均价：第1天[{avg_1}],第2天[{avg_2}],第3天[{avg_3}]")
    except:
        Log.info(f"{code}-{standardDay}不存在后3天日期数据")
    return count


check_error = {}


def existReleaseByLimitDay(xqDataList, standardDay, limitDay):
    """
    近limitDay天内存在 换手率放量
    :param xqDataList:
    :param standardDay:
    :param limitDay:
    :return:
    """
    exchange_ratio_10 = count_exchange_ratio(xqDataList, standardDay, 10)

    for i in range(limitDay):
        day = i + 1
        # 距离N天的换手率，例如day =1 就是standardDay当天的换手率
        exchange_day = count_exchange_ratio(xqDataList, standardDay, day + 1) - count_exchange_ratio(xqDataList,
                                                                                                     standardDay, day)
        if exchange_day >= exchange_ratio_10 * 0.2:
            return True
    return False


def check_condition1(xqDataList, code, standardDay):
    flag_error = 0
    flag = True

    # 排除跌停的数据
    if xqDataList.__contains__(standardDay) and flag:
        toDay_fudu = float(xqDataList[standardDay]['change_pct'])
        # 排除跌停
        if abs(toDay_fudu) > 9.5:
            flag_error = 1
            flag = False

    # 统计所有换手率
    exchange_ratio_10 = count_exchange_ratio(xqDataList, standardDay, 10)
    exchange_ratio_3 = count_exchange_ratio(xqDataList, standardDay, 3)
    exchange_ratio_2 = count_exchange_ratio(xqDataList, standardDay, 2)
    exchange_ratio_1 = count_exchange_ratio(xqDataList, standardDay, 1)
    exchange_ratio_10_min = get_min_exchange_ratio(xqDataList, standardDay, 10)

    # # 10日换手率<2的丢弃 成交量过低的舍弃
    # if exchange_ratio_10 <= 2 and flag:
    #     flag_error = 2
    #     flag = False
    # 前5日中存在放量10日均量 < 2倍以上的丢弃
    if not existReleaseByLimitDay(xqDataList,standardDay,5):
        flag_error = 3
        flag = False
    # 近2日出现缩量新低
    if (exchange_ratio_1 > exchange_ratio_10_min or (
            exchange_ratio_2 - exchange_ratio_1) > exchange_ratio_10_min) and flag:
        flag_error = 4
        flag = False
    # 近2日出现10日内最小振幅
    # 当天振幅额度 , 昨天振幅额度
    amplitude_limit_1 = get_amplitude_limit(xqDataList, standardDay, 1)
    amplitude_limit_2 = get_amplitude_limit(xqDataList, standardDay, 2)
    if (not (is_10_min(xqDataList, standardDay, amplitude_limit_1) or
             is_10_min(xqDataList, standardDay, amplitude_limit_2))) and flag:
        flag_error = 5
        flag = False

    # 近2日出现10日内新低
    lowest_price_1 = get_lowest_price(xqDataList, standardDay, 1)
    lowest_price_2 = get_lowest_price(xqDataList, standardDay, 2)
    if (not (is_10_minP(xqDataList, standardDay, lowest_price_1)
             or is_10_minP(xqDataList, standardDay, lowest_price_2))) and flag:
        flag_error = 6
        flag = False

    if not flag:
        Log.error(f"code{code},check_condition1失败-flag_error{flag_error}")

    return flag


def get_200_max(xqDataList, standardDay):
    standardDay = datetime.now().strftime(TIME_FORMAT)
    if xqDataList.__contains__('200_max_cha'):
        return xqDataList['200_max_cha']

    max = 0.0
    maxDay = standardDay
    for i in range(0, 195):
        one_cha = get_one_cha(xqDataList, standardDay)
        if max < one_cha:
            maxDay = standardDay
            max = one_cha
        standardDay = getPreviousTradedays(standardDay)
    data = {"max": max, "maxDay": maxDay}
    xqDataList.setdefault('200_max_cha', data)
    return data


# 因为除权 除息的关系导致不正确
def get_avg_1(xqDataList, standardDay):
    if xqDataList.__contains__(standardDay):
        xqDataList_normal = xqDataList['normal']
        avg = float(xqDataList[standardDay]['trade_val']) / float(xqDataList[standardDay]['trade_amount'])
        # 除权率
        chu = float(xqDataList[standardDay]['close_price']) / float(xqDataList_normal[standardDay]['close_price'])
        avg = avg * chu
        return avg
    else:
        return 0


def get_5_avg(xqDataList, standardDay):
    trade_amount_all = 0
    avg_5 = 0
    for i in range(0, 5):
        if xqDataList.__contains__(standardDay):
            avg_1 = get_avg_1(xqDataList, standardDay)
            today_trade_amount = float(xqDataList[standardDay]['trade_amount'])
            trade_amount_all = trade_amount_all + today_trade_amount

            avg_5 = avg_1 * (today_trade_amount / trade_amount_all) + avg_5 * (
                    1 - (today_trade_amount / trade_amount_all))

            standardDay = getPreviousTradedays(standardDay)
    return avg_5


def get_5_xielv(xqDataList, standardDay, limit, default=0.95):
    val = 0.5
    while True:
        preDay = getPreviousTradedays(standardDay)
        avg_5 = get_5_avg(xqDataList, standardDay)
        avg_5_pre = get_5_avg(xqDataList, preDay)
        # 除数不可为0
        if avg_5_pre == 0.0:
            break
        xielv = math.atan((avg_5 / avg_5_pre - 1) * 100) * 57.3;
        standardDay = preDay
        limit -= 1
        if limit < 1:
            val = math.sin(math.degrees(abs(int(xielv))))
            break
    if val <= default:
        val = default
    return val


def get_one_cha(xqDataList, standardDay):
    # 当天收盘价
    if xqDataList.__contains__(standardDay):
        toDay_lowest = float(xqDataList[standardDay]['lowest_price'])
        # 5日均价
        avg_5 = get_5_avg(xqDataList, standardDay)
        cha = avg_5 - toDay_lowest
        # Log.info(f"{standardDay}-5日均价为{avg_5}")
    else:
        cha = 0.0
    return cha


def check_condition2(xqDataList, code, standardDay):
    # 获取近200中当日最低价 偏离5日均线最大值
    data_200 = get_200_max(xqDataList, standardDay)
    max = data_200['max']
    maxDay = data_200['maxDay']
    val = get_5_xielv(xqDataList, standardDay, 1)
    one_cha = 0.0
    # 当天收盘价
    if xqDataList.__contains__(standardDay):
        toDay_close = float(xqDataList[standardDay]['close_price'])
        # 5日均价
        avg_5 = get_5_avg(xqDataList, standardDay)
        one_cha = avg_5 - toDay_close

    if one_cha < max * val:
        return False

    if xqDataList.__contains__(standardDay):
        toDay_close = float(xqDataList[standardDay]['close_price'])
        toDay_lowest = float(xqDataList[standardDay]['lowest_price'])
        toDay_fudu = float(xqDataList[standardDay]['change_pct'])
        if toDay_lowest == toDay_close and abs(toDay_fudu) > 9.8:
            return False

    Log.info(f"【{code}】:200日{maxDay}偏离最大值[{max}]斜率:[{val}],{standardDay}日偏离值{one_cha}")
    return True


def checkXQdata(xqDataList, code, standardDay):
    if xqDataList.__contains__(standardDay):
        flag = check_condition1(xqDataList, code, standardDay)
        flag2 = check_condition2(xqDataList, code, standardDay)

        count = checkRes(xqDataList, code, standardDay)
        if flag:
            key = f"{standardDay}#1"
            if rightMap.__contains__(key):
                rightStock = rightMap.get(key)
                rightStock.append({code: count})
                rightMap.setdefault(key, rightStock)
            else:
                rightMap.setdefault(key, [{code: count}])

        if flag2:
            key = f"{standardDay}#2"
            if rightMap.__contains__(key):
                rightStock = rightMap.get(key)
                rightStock.append({code: count})
                rightMap.setdefault(key, rightStock)
            else:
                rightMap.setdefault(key, [{code: count}])


def makeDayList(startDay, endDay):
    list = []
    day = startDay
    while True:
        if day <= endDay:
            list.append(day)
        else:
            break
        day = getNextTradedays(day)
    return list


standardDayList = makeDayList('20230701', '20230810')


def df(stockCodeStr):
    datas = str(stockCodeStr).split(".")
    code = str(datas[0])
    marketType = int(datas[1])

    if code.startswith('15') or code.startswith('16') \
            or code.startswith("68"):
        return
    Log.info(f"开始--{code}")

    try:
        xqDataList = getXueQiuKlineBefore(code, marketType, 200)
        # Log.info(f"雪球[{code}]数据{xqDataList}")
        xqDataList.setdefault("normal", getXueQiuKlineNormal(code, marketType, 200))
        # 筛选数据
        # standardDay = '20230410'

        for standardDay in standardDayList:
            if xqDataList.__contains__(standardDay):
                checkXQdata(xqDataList, code, standardDay)
    except:
        Log.error(f"code:{code},error{traceback.format_exc()}")

    Log.info(f"结束--{code}")


if __name__ == '__main__':
    stockList = stock_list
    pool = ThreadPool(8)
    pool.map(df, stockList)
    pool.close()  # 关闭进程池，不再接受新的进程
    pool.join()  # 主进程阻塞等待子进程的退出

    Log.info("结束-------")
    Log.info(rightMap)
    for standardDay in standardDayList:
        key = f"{standardDay}#1"
        if rightMap.__contains__(key):
            rightStock = rightMap.get(key)
            Log.info(f"day[{key}]:{rightStock}")
    for standardDay in standardDayList:
        key = f"{standardDay}#2"
        if rightMap.__contains__(key):
            rightStock = rightMap.get(key)
            Log.info(f"day[{key}]:{rightStock}")
    TcpService().close()
